package client

import (
	"context"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/slimtoolkit/slim/pkg/vulnerability/epss"
	"github.com/slimtoolkit/slim/pkg/vulnerability/epss/api"
)

type Instance struct {
	client *api.Instance
	debug  bool
	logger *log.Entry
}

type Options struct {
	APITimeout int
	Pretty     bool
	PageSize   uint64 //will be used as the default 'limit' value
	Debug      bool
	Logger     *log.Entry
}

type CallOptions struct {
	Date         time.Time
	PageSize     uint64 //will be used as the default 'limit' value
	Offset       uint64
	OutputFields []string
}

type FilteredCallOptions struct {
	CallOptions
	CveIDPattern   string
	DaysSinceAdded uint
	ScoreGt        float64
	ScoreLt        float64
	PercentileGt   float64
	PercentileLt   float64
	OrderRecords   epss.OrderType
}

func New(options ...Options) *Instance {
	var apiOptions []api.Options
	if len(options) > 0 {
		apiOptions = append(apiOptions, api.Options{
			APITimeout: options[0].APITimeout,
			Pretty:     options[0].Pretty,
			PageSize:   options[0].PageSize,
			Debug:      options[0].Debug,
			Logger:     options[0].Logger,
		})
	}

	ref := Instance{
		client: api.New(apiOptions...),
	}

	var logger *log.Entry
	if len(options) > 0 {
		ref.debug = options[0].Debug
		logger = options[0].Logger
	}

	if logger == nil {
		logger = log.NewEntry(log.StandardLogger())
	}

	ref.logger = logger.WithField("com", "epss.client")
	return &ref
}

func apiCallOptions(input []CallOptions) []api.CallOptions {
	var output []api.CallOptions
	if len(input) > 0 {
		output = append(output, api.CallOptions{
			Date:         epss.DateToString(input[0].Date),
			PageSize:     input[0].PageSize,
			Offset:       input[0].Offset,
			OutputFields: input[0].OutputFields,
		})
	}

	return output
}

func (ref *Instance) LookupScore(
	ctx context.Context,
	cveID string,
	options ...CallOptions) (*epss.Score, *epss.Result, error) {
	apiOptions := apiCallOptions(options)
	apiReply, err := ref.client.LookupCall(ctx, []string{cveID}, apiOptions...)
	if err != nil {
		return nil, nil, err
	}

	apiResult := apiReply.(*epss.APIResult)
	result, err := epss.NewResult(apiResult)
	if err != nil {
		return nil, nil, err
	}

	if len(apiResult.Data) == 0 {
		return nil, result, nil
	}

	return result.Data[0], result, nil
}

func (ref *Instance) LookupScoreWithHistory(
	ctx context.Context,
	cveID string,
	options ...CallOptions) (*epss.ScoreWithHistory, *epss.ResultWithHistory, error) {
	apiOptions := apiCallOptions(options)
	if len(options) == 0 {
		apiOptions = append(apiOptions, api.CallOptions{})
	}

	apiOptions[0].WithHistory = true
	apiReply, err := ref.client.LookupCall(ctx, []string{cveID}, apiOptions...)
	if err != nil {
		return nil, nil, err
	}

	apiResult := apiReply.(*epss.APIResultWithHistory)
	if len(apiResult.Data) == 0 {
		return nil, nil, nil
	}

	result, err := epss.NewResultWithHistory(apiResult)
	if err != nil {
		return nil, nil, err
	}

	return result.Data[0], result, nil
}

func (ref *Instance) LookupScores(
	ctx context.Context,
	cveIDs []string,
	options ...CallOptions) ([]*epss.Score, *epss.Result, error) {
	apiOptions := apiCallOptions(options)
	apiReply, err := ref.client.LookupCall(ctx, cveIDs, apiOptions...)
	if err != nil {
		return nil, nil, err
	}

	result, err := epss.NewResult(apiReply.(*epss.APIResult))
	if err != nil {
		return nil, nil, err
	}

	return result.Data, result, nil
}

func (ref *Instance) LookupScoresWithHistory(
	ctx context.Context,
	cveIDs []string,
	options ...CallOptions) ([]*epss.ScoreWithHistory, *epss.ResultWithHistory, error) {
	apiOptions := apiCallOptions(options)
	if len(options) == 0 {
		apiOptions = append(apiOptions, api.CallOptions{})
	}

	apiOptions[0].WithHistory = true
	apiReply, err := ref.client.LookupCall(ctx, cveIDs, apiOptions...)
	if err != nil {
		return nil, nil, err
	}

	result, err := epss.NewResultWithHistory(apiReply.(*epss.APIResultWithHistory))
	if err != nil {
		return nil, nil, err
	}

	return result.Data, result, nil
}

func (ref *Instance) ListScores(
	ctx context.Context,
	options ...FilteredCallOptions) ([]*epss.Score, *epss.Result, error) {
	apiOptions := apiFilteredCallOptions(options)
	apiReply, err := ref.client.ListCall(ctx, apiOptions...)
	if err != nil {
		return nil, nil, err
	}

	result, err := epss.NewResult(apiReply.(*epss.APIResult))
	if err != nil {
		return nil, nil, err
	}

	return result.Data, result, nil
}

func (ref *Instance) ListScoresWithHistory(
	ctx context.Context,
	options ...FilteredCallOptions) ([]*epss.ScoreWithHistory, *epss.ResultWithHistory, error) {
	apiOptions := apiFilteredCallOptions(options)
	if len(options) == 0 {
		apiOptions = append(apiOptions, api.FilteredCallOptions{})
	}

	apiOptions[0].WithHistory = true
	apiReply, err := ref.client.ListCall(ctx, apiOptions...)
	if err != nil {
		return nil, nil, err
	}

	result, err := epss.NewResultWithHistory(apiReply.(*epss.APIResultWithHistory))
	if err != nil {
		return nil, nil, err
	}

	return result.Data, result, nil
}

func apiFilteredCallOptions(input []FilteredCallOptions) []api.FilteredCallOptions {
	var output []api.FilteredCallOptions
	if len(input) > 0 {
		output = append(output, api.FilteredCallOptions{
			CallOptions: api.CallOptions{
				Date:         epss.DateToString(input[0].Date),
				PageSize:     input[0].PageSize,
				Offset:       input[0].Offset,
				OutputFields: input[0].OutputFields,
			},
			CveIDPattern:   input[0].CveIDPattern,
			DaysSinceAdded: input[0].DaysSinceAdded,
			ScoreGt:        input[0].ScoreGt,
			ScoreLt:        input[0].ScoreLt,
			PercentileGt:   input[0].PercentileGt,
			PercentileLt:   input[0].PercentileLt,
			OrderRecords:   input[0].OrderRecords,
		})
	}

	return output
}
